#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2025/6/3 19:58
# @Author  : wuyupeng
# @FileName: pypi_repo_manager.py
# @Software: PyCharm
import sys
import json
import logging
import requests
import threading
import subprocess
from pathlib import Path
from pkginfo import Wheel

from repo_uploaders.repo_manager import RepoManager, RepoManagerFactory

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")

EXIT_UPLOAD_PYPI_FAILED = 11


class SubprocessHelper:
    def __init__(self):
        self._stdout_thread = None
        self._stderr_thread = None
        self._proc = None

    def run(self, cmd):
        self._proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
        self._stdout_thread = threading.Thread(target=self._info_output)
        self._stderr_thread = threading.Thread(target=self._error_output)

        self._stdout_thread.start()
        self._stderr_thread.start()

        self._stdout_thread.join()
        self._stdout_thread.join()

        self._proc.wait()

    def _info_output(self):
        for line in self._proc.stdout:
            if line.strip():
                logging.info(line)

    def _error_output(self):
        for line in self._proc.stderr:
            if line.strip():
                logging.error(line)

    @property
    def returncode(self):
        return self._proc.returncode


@RepoManagerFactory.register("pypi")
class PypiRepoManager(RepoManager):
    def __init__(self, pkg_path, pypi_token, use_test=False, is_debug=False):
        super().__init__(pkg_path, is_debug)
        self._username = "__token__"
        self._token = pypi_token
        self._use_test = use_test

    def construct_package_locally(self, *args, **kwargs):
        return self._pkg_path

    def parse_name_and_version(self):
        if not self._token:
            raise EnvironmentError(f"无效的 pypi token! ")
    
        if not self._pkg_path.exists() or not self._pkg_path.is_file() or self._pkg_path.suffix != ".whl":
            raise FileNotFoundError(f"Wheel 文件不存在：{self._pkg_path}")
        
        meta = Wheel(self._pkg_path)
        self._pkg_name = meta.name
        self._pkg_version = meta.version

    def check_version_exist(self):
        if self._use_test:
            pypi_header = "https://test.pypi.org/pypi/"
        else:
            pypi_header = "https://pypi.org/pypi/" 

        pypi_resp = requests.get(f"{pypi_header}{self._pkg_name}/json")
        if pypi_resp.ok:
            pypi_data = json.loads(pypi_resp.text)
            logging.info(f"当前 pypi包版本: {self._pkg_version}")
            logging.info(f"可获取版本：{pypi_data["releases"].keys()}")
            if self._pkg_version in pypi_data["releases"]:
                logging.info(f"{self._pkg_name}/{self._pkg_version} 已存在，停止上传 {self._pkg_path}")
                return True
        
        return False
    
    def push_to_repo(self, pkg_root: Path, *args, **kwargs):
        msg = "使用测试场景"
        if not self._use_test:
            msg = "不" + msg
        self.debug(msg)

        if not pkg_root.exists():
            raise ValueError(f"包路径不存在: {pkg_root}")

        if self._use_test:
            upload_whl = [
                "twine", "upload", "--verbose",
                "--repository", "testpypi",
                "--username", self._username,
                "--password", self._token,
                str(pkg_root)
            ]
        else:
            upload_whl = [
                "twine", "upload", "--verbose",
                "--username", self._username,
                "--password", self._token,
                str(pkg_root)
            ]

        try:
            proc = SubprocessHelper()
            proc.run(upload_whl)
            if proc.returncode != 0:
                logging.error("上传 PyPi 失败！")
                sys.exit(EXIT_UPLOAD_PYPI_FAILED)
            else:
                logging.info("上传 PyPi 成功！")
        except subprocess.CalledProcessError as e:
            logging.error(str(e))
            logging.error("上传 PyPi 失败！")
            sys.exit(EXIT_UPLOAD_PYPI_FAILED)

    def delete_package(self, *args, **kwargs) -> bool:
        pass
